import os, sys
from twisted.web import xmlrpc
from twisted.internet import reactor

APPLICATION_INSTALL_DIR = os.environ.get('SIMPLEWATCHER_INSTALL')
if not APPLICATION_INSTALL_DIR:
    raise EnvironmentError("must set environment variable SIMPLEWATCHER_INSTALL")
sys.path.append('%s/lib' % APPLICATION_INSTALL_DIR)

from watcher.config import ConfigLoader, DEFAULT_CONFIG_PATH
from watcher.config.classbuilder import KlassBuilder

from watcher.exceptions import WatcherError, WatcherConfigError
from watcher import WatcherContainer


class WatcherServer(xmlrpc.XMLRPC):
    """the watch server should provide the following functionality:
       listen on a a network socket for specific instructions.  this will be
       done with twisted's xmlrpc server.
    
       available methods (that I know of right now):
       
       ### watch ctl ###
       startWatch(name)
       stopWatch(name)
       restartWatch(name)
       watchInfo(name)

    """
    def __init__(self, configName, configPath=None):
        self.reactor = reactor
        if configPath is None:
            configPath = DEFAULT_CONFIG_PATH
        server_name = "server_" + configName
        self.config = ConfigLoader(configPath).load(server_name)
        watcher_name = "watcher_" + str(self.config.watcher.xml_text_content())
        self.watcher = Watcher(watcher_name, configPath=configPath, parent=self)
        self.watcher.baseInit()

    def xmlrpc_startWatch(self, name):
        try:
            self.watcher.startWatch(name)
        except:
            raise
        return True

    def xmlrpc_stopWatch(self, name):
        try:
            self.watcher.stopWatch(name)
        except:
            return False
        return True
    
    def xmlrpc_restartWatch(self, name):
        """we could get more conditional with the try/excepts
           and attempt to determine what failed where, and give
           back a meaningful response.  or, we can assume we
           don't care right now why it worked or didn't, and 
           try to do both at once.  if it works, great.  if not, 
           fail
        """
        try:
            self.watcher.stopWatch(name)
            self.watcher.startWatch(name)
        except:
            return False
        return True

    def xmlrpc_watchInfo(self, name):
        return self.watcher.children[name]['watch'].xml()
    
    def stopService(self):
        for child in self.watcher.children:
            self.watcher.stopWatch(child)
        super(WatcherServer, self).stopService()



class Watcher(WatcherContainer):

    def __init__(self, *args, **kwargs):
        super(Watcher, self).__init__(*args, **kwargs)
        """ super provides a self.children, as well as a few
            methods for adding/removing those children.  the default
            way of using children, while not bad for the concept of 
            simple containment, doesn't do everything i need it to do,
            so i'm using the provided self.children in a nonstandard way
        """

    def _tryWatch(self, file):
        """
           hidden directories and files are skipped.  if it's a directory
           recurse down in.
           
           protocol and schedule are reserved words to describe other
           necessary class instantiations.
        """
        try:
            if os.path.basename(file)[0] == ".":
                return
        except IndexError:
            pass

        try:
            for f in os.listdir(file):
                self._tryWatch(os.path.abspath(os.path.join(file, f)))
        except OSError:
            file_path = os.path.dirname(file)
            file = os.path.basename(file)
            if (file[0:6] == "watch_") and (file[6:14] not in ["protocol", "schedule"]):
                name = file[6:-4]
                projectDir = os.path.join(file_path[0:-len(name)], name)
                if ConfigLoader(projectDir).load("watch_"+name).startOnInit == "True":
                    self.startWatch(name)
                    
                
    def baseInit(self):
        """attempt to spawn all watches in the current config dir and all
           recursive sub dirs
        """
        for file in os.listdir(self.configPath):
            self._tryWatch(os.path.abspath(os.path.join(self.configPath, file)))
            

    def startWatch(self, name):
        try:
            self._spawn(name)
        except WatcherConfigError, e:
            """just catching so i can log and such later"""
            raise e
        except WatcherError, e:
            """just catching so i can log and such later"""
            raise e

    def stopWatch(self, name):
        try:
            self.children[name]['protocol'].die()
        except:
            raise
        del(self.children[name])
        

    def _spawn(self, name):
        """ see the docs/launchers.txt for details, but in effect
            we instantiate a process protocol for twisted, and then
            run the launcher in it's config, which should be a script
            in APPLICATION_INSTALL_DIR/launchers
        """
        # here we assume that we're starting all watches
        # from a dir beneath the server.xml config dir
        # named for the project
        projectPath = os.path.join(self.configPath, name)
        if name in self.children:
            raise WatcherError("Watch already in progress")
        protocol = "watch_protocol"+"_"+name
        # instantiate a protocol class that can be spawned by reactor
        wp = KlassBuilder(protocol, projectPath)()(protocol, projectPath)
        # protocol configs are required to provide executable.bin
        prog = wp.config.executable.bin.xml_text_content()
        args = [prog]
        for arg in wp.config.executable.argument:
            args.append(arg.xml_text_content())
        env = os.environ
        env['SIMPLEWATCHER_INSTALL'] = APPLICATION_INSTALL_DIR
        for v in wp.config.environment.var:
            envval = u""
            for val in v.value:
                envval += u":" + unicode(val.xml_text_content())
            if v.type == u"update":
                if not v.name in env:
                    env[v.name] = u""
                env[v.name] += env[v.name] + envval
            elif v.type == u"overlay":
                env[v.name] = envval
                
            
        # spawn process
        self.parent.reactor.spawnProcess(wp, prog, args=args, env=env)
        watchConfig = ConfigLoader(projectPath).load('watch_'+wp.config.watch.name)
        self.children[name] = {'protocol' : wp, 'watch' : watchConfig}



if __name__ == "__main__":
    from optparse import OptionParser
    usage = "usage: %prog {-n, --name} servername {-p, --path} configpath"
    parser = OptionParser(usage)
    parser.add_option("-n", "--name", dest="name", help="name of the server to start")
    parser.add_option("-p", "--path", dest="path", help="configuration path")
    (options, args) = parser.parse_args()
    name = options.name
    configPath = options.path
    if not name:
        parser.error("Must have server name")

                                        
    from twisted.web import server, resource
    """run server"""
    root = resource.Resource()
    ws = WatcherServer(name, configPath)
    root.putChild('RPC2', ws)
    site = server.Site(root)
    ws.reactor.listenTCP(int(ws.config.listen_port.xml_text_content()), site)
    ws.reactor.run()
